﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using Less.Core;
using Less.Tools;
using PetaPoco;

/// <summary>
/// Derived database class with custom methods
/// </summary>
public class LessDatabase : Database
{
    public object TokenId = new object();
    private DateTime _start;

    public LessDatabase(IDbConnection connection)
        : base(connection)
    {
    }

    public LessDatabase(string connectionString, string providerName)
        : base(connectionString, providerName)
    {
    }

    public LessDatabase(string connectionString, DbProviderFactory provider)
        : base(connectionString, provider)
    {
    }

    public LessDatabase(string connectionStringName)
        : base(connectionStringName)
    {
    }

    public LessDatabase()
        : base(Data.GetConnectionString(), Data.GetProviderName())
    {
    }

    #region CRUD Methods

    public List<T> SelectList<T>(string comand, params object[] args)
    {
        List<T> list;
        try
        {
            list = base.Fetch<T>(comand, args);
        }
        catch (Exception ex)
        {
            throw CreateDatabaseException(ex);
        }
        Log.Metric(EntryCategories.Database, metricName: "RecordsSelected",metricValue: list.HasValue() ? list.Count.ToString() : "0");
        return list;
    }

    public T Select<T>(string comand, params object[] args) where T : class
    {
        T item;
        try
        {
            try
            {
                Log.Debug(comand);
                item = base.SingleOrDefault<T>(comand, args);
            }
            catch (Exception ex)
            {
                Log.Debug(ex.ToLogString());
                Log.Warning("Too many results for: " + (comand));
                item = SelectList<T>(comand).FirstOrDefault();
            }
        }
        catch (Exception ex)
        {
            throw CreateDatabaseException(ex);
        }
        Log.Metric(EntryCategories.Database, metricName: "RecordsSelected",metricValue: item != null ? "1" : "0");
        return item;
    }

    public object Insert(string tableName, object poco)
    {
        object result;
        lock (TokenId)
        {
            try
            {
                result = base.Insert(tableName, "Id", poco);
                if (result != null)
                {
                    long id = 0;
                    if (long.TryParse(result.ToString(), out id)) 
                        Functions.SetValue(poco, "Id", id);
                }
            }
            catch (Exception ex)
            {
                throw CreateDatabaseException(ex);
            }
        }
        if (!(poco is LogEntry))
            Log.Metric(EntryCategories.Database, metricName: "RecordsInserted", metricValue: "1");
        return result;
    }


    public int Update(string tableName, object poco)
    {
        int result = 0;
        try
        {
            result = base.Update(tableName, "Id", poco);
        }
        catch (Exception ex)
        {
            throw CreateDatabaseException(ex);
        }
        if (result > 0)
            Log.Metric(EntryCategories.Database, metricName: "RecordsUpdated", metricValue: "1");
        return result;
    }

    public int Delete(string tableName, object poco)
    {
        int result = base.Delete(tableName, "Id", poco);
        if (result > 0)
            Log.Metric(EntryCategories.Database, metricName: "RecordsDeleted", metricValue: "1");
        return result;
    }

    public new T ExecuteScalar<T>(string sql, params object[] args)
    {
        T result;
        try
        {
            result = base.ExecuteScalar<T>(sql, args);
        }
        catch (Exception ex)
        {
            throw CreateDatabaseException(ex);
        }
        return result;
    }

    public new int Execute(string sql, params object[] args)
    {
        int result;
        try
        {
            result = base.Execute(sql, args);
        }
        catch (Exception ex)
        {
            throw CreateDatabaseException(ex);
        }
        return result;
    }

    #endregion

    public override void OnExecutingCommand(IDbCommand cmd)
    {
        if (Log.EnableMetrics)
        {
            _start = DateTime.Now;
        }
    }

    public override void OnExecutedCommand(IDbCommand cmd)
    {
        if (!Log.EnableMetrics) return;
        double timeSpan = DateTime.Now.Subtract(_start).TotalSeconds;
        if (timeSpan > 1)
        {
            Log.ProcessEnd(processName: LastCommand, metricName: "Time", metricValue: timeSpan);
        }
    }

    private Exception CreateDatabaseException(Exception e)
    {
        int i = 2;
        string callingMethod;
        do
        {
            callingMethod = Functions.GetCallingMethodName(i);
            i++;
        } while (callingMethod.Contains("PetaPoco") || callingMethod.Contains("LessDatabase"));
        var sqlCommandEx = new SqlCommandException(LastCommand, callingMethod, e);
        return sqlCommandEx;
    }
}